//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#pragma once

#include "WeUtilPrerequisites.h"

#include "WeVector3.h"
#include "WeMatrix2.h"

namespace WackiEngine
{
	
	template<class T>
	class Matrix4;

	//-------------------------------------
	// Matrix3
	template<class T>
	class Matrix3 {
		friend class Matrix4<T>;

	protected:
		T	_m[3][3];

	public:
		Matrix3();
		Matrix3(const T m0, const T m1, const T m2,
			const T m3, const T m4, const T m5,
			const T m6, const T m7, const T m8);
		Matrix3(const Matrix2<T> &A);
		Matrix3(const Matrix3 &A);

		Matrix3(const T degAng, const T rx, const T ry, const T rz);	//rotation constructor

		//setters
		void			setMatrix(	const T m0 = 1, const T m1 = 0, const T m2 = 0,
									const T m3 = 0, const T m4 = 1, const T m5 = 0,
									const T m6 = 0, const T m7 = 0, const T m8 = 1);

		void			setMatrix(const Matrix2<T> &A);
		void			setMatrix(const Matrix3 &A);

		//getters
		T*				m() { return _m; }
		T				m(uint32 row, uint32 col)	{ return _m[row][col]; }

		void			multiply(const Matrix3 &A);

		//operators	
		Matrix3<T>&	operator=	(const Matrix3& A);			//assignment
		Matrix3<T>	operator*	(const Matrix3& A) const;		//matrix * matrix
		Matrix3<T>&	operator*=	(const Matrix3& A);			//matrix *= matrix
		Vector3<T>	operator*	(const Vector3<T>& v) const;	//matrix * vector3
		Matrix3<T>	operator*	(const T s) const;				//matrix * scalar
		Matrix3<T>&	operator*=	(const T s);					//matrix *= scalar
		Matrix3<T>	operator/	(const T s) const;				//matrix / scalar
		Matrix3<T>&	operator/=	(const T s);					//matrix /= scalar
		Matrix3<T>	operator+	(const Matrix3& A) const;		//matrix + matrix
		Matrix3<T>&	operator+=	(const Matrix3& A);			//matrix += matrix
		Matrix3<T>	operator-	(const Matrix3& A) const;		//matrix - matrix
		Matrix3<T>&	operator-=	(const Matrix3& A);			//matrix -= matrix

		//transformations
		void			scale(const T sxyz);
		void			scale(const T sx, const T sy, const T sz);
		void			scale(const Vector3<T>& sxyz);

		void			rotate(const T degAng, const T x, const T y, const T z);
		void			rotate(const T degAng, const Vector3<T>& axis);
		void			rotation(const T degAng, const T x, const T y, const T z);	//set rotation

		void			identity();
		Matrix3<T>		transposed() const;
		void			transpose();
		//TODO: inverse etc
	};

	//constructors
	template<class T>
	Matrix3<T>::Matrix3()
	{
		identity();
	}

	template<class T>
	Matrix3<T>::Matrix3(T m0, T m1, T m2,
		T m3, T m4, T m5,
		T m6, T m7, T m8)
	{
		setMatrix(m0, m1, m2,
			m3, m4, m5,
			m6, m7, m8);
	}

	template<class T>
	Matrix3<T>::Matrix3(const Matrix2<T> &A)
	{
		setMatrix(A);
	}

	template<class T>
	Matrix3<T>::Matrix3(const Matrix3 &A)
	{
		setMatrix(A);
	}

	template<class T>
	Matrix3<T>::Matrix3(const T degAng, const T rx, const T ry, const T rz)
	{
		identity();
		rotation(degAng, rx, ry, rz);
	}

	//setters
	template<class T>
	void Matrix3<T>::setMatrix(T m0, T m1, T m2,
		T m3, T m4, T m5,
		T m6, T m7, T m8)
	{
		_m[0][0] = m0;	_m[0][1] = m1;	_m[0][2] = m2;
		_m[1][0] = m3;	_m[1][1] = m4;	_m[1][2] = m5;
		_m[2][0] = m6;	_m[2][1] = m7;	_m[2][2] = m8;
	}

	template<class T>
	void Matrix3<T>::setMatrix(const Matrix2<T> &A)
	{
		_m[0][0] = A._m[0][0];	_m[0][1] = A._m[0][1];	_m[0][2] = 0;
		_m[1][0] = A._m[1][0];	_m[1][1] = A._m[1][1];	_m[1][2] = 0;
		_m[2][0] = 0;			_m[2][1] = 0;			_m[2][2] = 1;
	}

	template<class T>
	void Matrix3<T>::setMatrix(const Matrix3 &A)
	{
		_m[0][0] = A._m[0][0];	_m[0][1] = A._m[0][1];	_m[0][2] = A._m[0][2];
		_m[1][0] = A._m[1][0];	_m[1][1] = A._m[1][1];	_m[1][2] = A._m[1][2];
		_m[2][0] = A._m[2][0];	_m[2][1] = A._m[2][1];	_m[2][2] = A._m[2][2];
	}

	template<class T>
	void Matrix3<T>::multiply(const Matrix3 &A)
	{
		setMatrix(_m[0][0] * A._m[0][0] + _m[0][1] * A._m[1][0] + _m[0][2] * A._m[2][0],	//row 1
			_m[0][0] * A._m[0][1] + _m[0][1] * A._m[1][1] + _m[0][2] * A._m[2][1],
			_m[0][0] * A._m[0][2] + _m[0][1] * A._m[1][2] + _m[0][2] * A._m[2][2],

			_m[1][0] * A._m[0][0] + _m[1][1] * A._m[1][0] + _m[1][2] * A._m[2][0],	//row 2
			_m[1][0] * A._m[0][1] + _m[1][1] * A._m[1][1] + _m[1][2] * A._m[2][1],
			_m[1][0] * A._m[0][2] + _m[1][1] * A._m[1][2] + _m[1][2] * A._m[2][2],

			_m[2][0] * A._m[0][0] + _m[2][1] * A._m[1][0] + _m[2][2] * A._m[2][0],	//row 3
			_m[2][0] * A._m[0][1] + _m[2][1] * A._m[1][1] + _m[2][2] * A._m[2][1],
			_m[2][0] * A._m[0][2] + _m[2][1] * A._m[1][2] + _m[2][2] * A._m[2][2],

			_m[3][0] * A._m[0][0] + _m[3][1] * A._m[1][0] + _m[3][2] * A._m[2][0],	//row 4
			_m[3][0] * A._m[0][1] + _m[3][1] * A._m[1][1] + _m[3][2] * A._m[2][1],
			_m[3][0] * A._m[0][2] + _m[3][1] * A._m[1][2] + _m[3][2] * A._m[2][2]);
	}

	//operators	
	template<class T>
	Matrix3<T>& Matrix3<T>::operator=(const Matrix3 &A)
	{
		_m[0][0] = A._m[0][0];	_m[0][1] = A._m[0][1];	_m[0][2] = A._m[0][2];
		_m[1][0] = A._m[1][0];	_m[1][1] = A._m[1][1];	_m[1][2] = A._m[1][2];
		_m[2][0] = A._m[2][0];	_m[2][1] = A._m[2][1];	_m[2][2] = A._m[2][2];

		return *this;
	}

	template<class T>
	Matrix3<T> Matrix3<T>::operator*(const Matrix3 &A) const
	{
		Matrix4<T> result((*this));
		result.multiply(A);
		return result;
	}

	template<class T>
	Matrix3<T>& Matrix3<T>::operator*=(const Matrix3 &A)
	{
		multiply(A);
		return *this;
	}

	//Matrix - 3D vector multiplication with perspective division
	template<class T>
	Vector3<T> Matrix3<T>::operator*(const Vector3<T> &v) const
	{
		return Vector4<T>(_m[0][0] * v.x + _m[0][1] * v.y + _m[0][2] * v.z,
			_m[1][0] * v.x + _m[1][1] * v.y + _m[1][2] * v.z,
			_m[2][0] * v.x + _m[2][1] * v.y + _m[2][2] * v.z);
	}

	template<class T>
	Matrix3<T> Matrix3<T>::operator*(const T s) const
	{
		return Matrix3<T>(_m[0][0] * s, _m[0][1] * s, _m[0][2] * s,
			_m[1][0] * s, _m[1][1] * s, _m[1][2] * s,
			_m[2][0] * s, _m[2][1] * s, _m[2][2] * s);
	}

	template<class T>
	Matrix3<T>& Matrix3<T>::operator*=(const T s)
	{
		for(int col = 0; col < 3; ++col)
		for(int row = 0; row < 3; ++row)
			_m[row][col] *= s;

		return *this;
	}

	template<class T>
	Matrix3<T> Matrix3<T>::operator/(const T s) const
	{
		return Matrix4<T>(_m[0][0] / s, _m[0][1] / s, _m[0][2] / s,
			_m[1][0] / s, _m[1][1] / s, _m[1][2] / s,
			_m[2][0] / s, _m[2][1] / s, _m[2][2] / s);
	}

	template<class T>
	Matrix3<T>& Matrix3<T>::operator/=(const T s)
	{
		for(int col = 0; col < 3; ++col)
		for(int row = 0; row < 3; ++row)
			_m[row][col] /= s;

		return *this;
	}

	template<class T>
	Matrix3<T> Matrix3<T>::operator+(const Matrix3 &A) const
	{
		return Matrix4<T>(_m[0][0] + A._m[0][0], _m[0][1] + A._m[0][1], _m[0][2] + A._m[0][2],
			_m[1][0] + A._m[1][0], _m[1][1] + A._m[1][1], _m[1][2] + A._m[1][2],
			_m[2][0] + A._m[2][0], _m[2][1] + A._m[2][1], _m[2][2] + A._m[2][2]);
	}

	template<class T>
	Matrix3<T>& Matrix3<T>::operator+=(const Matrix3 &A)
	{
		for(int col = 0; col < 3; ++col)
		for(int row = 0; row < 3; ++row)
			_m[row][col] += A._m[row][col];

		return *this;
	}

	template<class T>
	Matrix3<T> Matrix3<T>::operator-(const Matrix3 &A) const
	{
		return Matrix3<T>(_m[0][0] - A._m[0][0], _m[0][1] - A._m[0][1], _m[0][2] - A._m[0][2],
			_m[1][0] - A._m[1][0], _m[1][1] - A._m[1][1], _m[1][2] - A._m[1][2],
			_m[2][0] - A._m[2][0], _m[2][1] - A._m[2][1], _m[2][2] - A._m[2][2]);
	}

	template<class T>
	Matrix3<T>& Matrix3<T>::operator-=(const Matrix3 &A)
	{
		for(int col = 0; col < 3; ++col)
		for(int row = 0; row < 3; ++row)
			_m[row][col] -= A._m[row][col];

		return *this;
	}

	template<class T>
	void Matrix3<T>::scale(const T s)
	{
		Matrix4<T> S(s, 0, 0,
			0, s, 0,
			0, 0, s);
		multiply(S);
	}

	template<class T>
	void Matrix3<T>::scale(const T sx, const T sy, const T sz)
	{
		Matrix4<T> S(sx, 0, 0,
			0, sy, 0,
			0, 0, sz);
		multiply(S);
	}

	template<class T>
	void Matrix3<T>::scale(const Vector3<T> &s)
	{
		Matrix4<T> S(s.x, 0, 0,
			0, s.x, 0,
			0, 0, s.z);
		multiply(S);
	}

	template<class T>
	void Matrix3<T>::rotate(const T degAng, const T x, const T y, const T z)
	{
		Matrix4<T> rot(degAng, x, y, z);
		multiply(rot);
	}

	template<class T>
	void Matrix3<T>::rotate(const T degAng, const Vector3<T> &axis)
	{
		Matrix4<T> rot(degAng, axis.x, axis.y, axis.z);
		multiply(rot);
	}

	template<class T>
	void Matrix3<T>::rotation(const T degAng, const T axisx, const T axisy, const T axisz)
	{
		T radAng = (T)degAng*DEG2RAD;
		T ca = (T)cos(radAng), sa = (T)sin(radAng);
		if(axisx == 1 && axisy == 0 && axisz == 0)				// about x-axis
		{
			_m[0][0] = 1; _m[0][1] = 0;  _m[0][2] = 0;
			_m[1][0] = 0; _m[1][1] = ca; _m[1][2] = -sa;
			_m[2][0] = 0; _m[2][1] = sa; _m[2][2] = ca;
		}
		else if(axisx == 0 && axisy == 1 && axisz == 0)			// about y-axis
		{
			_m[0][0] = ca;  _m[0][1] = 0; _m[0][2] = sa;
			_m[1][0] = 0;   _m[1][1] = 1; _m[1][2] = 0;
			_m[2][0] = -sa; _m[2][1] = 0; _m[2][2] = ca;
		}
		else if(axisx == 0 && axisy == 0 && axisz == 1)			// about z-axis
		{
			_m[0][0] = ca; _m[0][1] = -sa; _m[0][2] = 0;
			_m[1][0] = sa; _m[1][1] = ca;  _m[1][2] = 0;
			_m[2][0] = 0;  _m[2][1] = 0;   _m[2][2] = 1;
		}
		else												// arbitrary axis
		{
			T l = axisx*axisx + axisy*axisy + axisz*axisz;  // length squared
			T x, y, z;
			x = axisx, y = axisy, z = axisz;
			if((l > T(1.0001) || l < T(0.9999)) && l != 0)
			{
				l = T(1.0) / sqrt(l);
				x *= l; y *= l; z *= l;
			}

			T xy = x*y, yz = y*z, xz = x*z, xx = x*x, yy = y*y, zz = z*z;
			_m[0][0] = xx + ca*(1 - xx);     _m[0][1] = xy - xy*ca - z*sa;  _m[0][2] = xz - xz*ca + y*sa;
			_m[1][0] = xy - xy*ca + z*sa;  _m[1][1] = yy + ca*(1 - yy);     _m[1][2] = yz - yz*ca - x*sa;
			_m[2][0] = xz - xz*ca - y*sa;  _m[2][1] = yz - yz*ca + x*sa;  _m[2][2] = zz + ca*(1 - zz);
		}
	}

	template<class T>
	Matrix3<T> Matrix3<T>::transposed() const
	{
		Matrix4<T> result((*this));
		result.transpose();
		return result;
	}

	template<class T>
	void Matrix3<T>::transpose()
	{
		swap(_m[0][1], _m[1][0]);
		swap(_m[0][2], _m[2][0]);
		swap(_m[1][2], _m[2][1]);
	}

	template<class T>
	void Matrix3<T>::identity()
	{
		_m[0][0] = _m[1][1] = _m[2][2] = 1;
		_m[0][1] = _m[0][2] = _m[1][0] = _m[1][2] = _m[2][0] = _m[2][1] == 0;
	}

	typedef Matrix3<float> Matrix3f;

}